﻿using System;
using System.Linq.Expressions;
using System.Reflection;
using System.Text;
using Remotion.Linq.Clauses.Expressions;
using Remotion.Linq.Parsing;

namespace FluentDml.Linq
{
    internal class HqlGeneratorExpressionTreeVisitor : ExpressionTreeVisitor
    {
        //public static string GetHqlExpression(Expression linqExpression, ParameterAggregator parameterAggregator)
        //{
        //    var visitor = new HqlGeneratorExpressionTreeVisitor(parameterAggregator);
        //    visitor.VisitExpression(linqExpression);
        //    return visitor.GetHqlExpression();
        //}

        private readonly StringBuilder _hqlExpression = new StringBuilder();
        private readonly ParameterAggregator _parameterAggregator;
        private bool _isBody = false;
        private bool _subQueryReconmended = false;

        public HqlGeneratorExpressionTreeVisitor(ParameterAggregator parameterAggregator)
        {
            _parameterAggregator = parameterAggregator;
        }

        public string GetHqlExpression()
        {
            return _hqlExpression.ToString();
        }

        public bool SubQueryReconmended { get { return _subQueryReconmended; } }

        protected override Expression VisitQuerySourceReferenceExpression(QuerySourceReferenceExpression expression)
        {
            _hqlExpression.Append(expression.ReferencedQuerySource.ItemName);
            return expression;
        }

        protected override Expression VisitUnaryExpression(UnaryExpression expression)
        {
            _hqlExpression.Append("(");
            switch (expression.NodeType)
            {
                case ExpressionType.Not:
                    _hqlExpression.Append(" not (");
                    VisitExpression(expression.Operand);
                    _hqlExpression.Append(")");
                    break;
                default:
                    base.VisitUnaryExpression(expression);
                    break;
            }

            

            _hqlExpression.Append(")");
            return expression;
        }

        protected override Expression VisitBinaryExpression(BinaryExpression expression)
        {


            _hqlExpression.Append("(");

            VisitExpression(expression.Left);
            ConstantExpression constantExpression = expression.Right as ConstantExpression;
            bool valueIsNull = (constantExpression != null && constantExpression.Value == null);

            // In production code, handle this via lookup tables.
            switch (expression.NodeType)
            {
                case ExpressionType.NotEqual:
                    //TODO not equal
                    _hqlExpression.Append(valueIsNull ? " is not " : " != ");
                    break;
                case ExpressionType.Equal:
                    _hqlExpression.Append(valueIsNull ? " is " : " = ");
                    break;

                case ExpressionType.AndAlso:
                case ExpressionType.And:
                    _hqlExpression.Append(" and ");
                    break;

                case ExpressionType.OrElse:
                case ExpressionType.Or:
                    _hqlExpression.Append(" or ");
                    break;

                case ExpressionType.Add:
                    _hqlExpression.Append(" + ");
                    break;

                case ExpressionType.Subtract:
                    _hqlExpression.Append(" - ");
                    break;

                case ExpressionType.LessThan:
                    _hqlExpression.Append(" < ");
                    break;

                case ExpressionType.LessThanOrEqual:
                    _hqlExpression.Append(" <= ");
                    break;

                case ExpressionType.GreaterThan:
                    _hqlExpression.Append(" > ");
                    break;

                case ExpressionType.GreaterThanOrEqual:
                    _hqlExpression.Append(" >= ");
                    break;

                case ExpressionType.Multiply:
                    _hqlExpression.Append(" * ");
                    break;

                case ExpressionType.Divide:
                    _hqlExpression.Append(" / ");
                    break;

                default:
                    base.VisitBinaryExpression(expression);
                    break;
            }

            VisitExpression(expression.Right);
            _hqlExpression.Append(")");

            return expression;
        }

        protected override Expression VisitMemberExpression(MemberExpression expression)
        {
            CheckForImplicitJoin(expression); //DML cannot do implicit joins

            VisitExpression(expression.Expression);
            _hqlExpression.AppendFormat(".{0}", expression.Member.Name);

            return expression;
        }

        private void CheckForImplicitJoin(MemberExpression expression)
        {
            if (SubQueryReconmended) return;

            Type type = expression.Type;
            if (type.IsClass && type != typeof(string))
            {
                _subQueryReconmended = true;
            }
        }

        protected override Expression VisitParameterExpression(ParameterExpression expression)
        {
            if (_isBody)
            {
                _hqlExpression.Append("x");
            }
            else
            {
                _isBody = true;
            }

            return expression;
        }

        protected override Expression VisitConstantExpression(ConstantExpression expression)
        {
            var namedParameter = _parameterAggregator.AddParameter(expression.Value);
            _hqlExpression.AppendFormat(":{0}", namedParameter.Name);

            return expression;
        }

        protected override Expression VisitMethodCallExpression(MethodCallExpression expression)
        {
            //for contains in a collection use ? in elements(collection)

            //lazy, the original documentation made reference to making a lookup
            //use a typeof
            var method = expression.Method;
            Type declaringType = method.DeclaringType;

            if (declaringType == typeof(string))
            {
                StringCallMethod(expression);
                return expression;
            }

            return base.VisitMethodCallExpression(expression); // throws
        }




        private void StringCallMethod(MethodCallExpression expression)
        {
            var method = expression.Method;
            var startsWith = method.Name.Equals("StartsWith");
            var endsWith = method.Name.Equals("EndsWith");
            var contains = method.Name.Equals("Contains");
            var supported = contains || startsWith || endsWith;

            //think about starting a watch
            ConstantExpression constantExpression = expression.Arguments[0] as ConstantExpression;
            if (!supported || constantExpression == null)
            {
                throw new NotSupportedException("cannot handle a contains/startswith/endswith which contains operations ie str1 + str2");
            }

            _hqlExpression.Append("(");
            VisitExpression(expression.Object);
            _hqlExpression.Append(" like ");

            StringBuilder value = new StringBuilder();

            if (contains || endsWith)
            {
                value.Append("%");
            }

            value.Append(constantExpression.Value.ToString());

            if (contains || startsWith)
            {
                value.Append("%");
            }

            //value.Append(constantExpression.Value);
            var namedParameter = _parameterAggregator.AddParameter(value.ToString());
            _hqlExpression.AppendFormat(":{0}", namedParameter.Name);


            _hqlExpression.Append(")");

        }
    }
}